using System;
using System.Collections.Generic;
using System.Threading;
using IFXplus.SmartStorage.Settings;

namespace IFXplus.SmartStorage.Providers
{
    internal class MemoryStorageProvider : IStorageProvider
    {
        #region Privates

        private readonly StorageProviderReference _reference = null;
        private readonly MemoryStorageProviderSettings _settings = null;

        private Dictionary<string, StorageEntry> _items = new Dictionary<string, StorageEntry>();
        private static readonly int ReaderLockTimeout = Timeout.Infinite;
        private static readonly int WriterLockTimeout = Timeout.Infinite;
        private static readonly ReaderWriterLock _sync = new ReaderWriterLock();

        #endregion

        #region Constructor

        public MemoryStorageProvider(StorageProviderReference reference)
        {
            if (reference == null)
                throw new ArgumentNullException("reference");
            if (string.IsNullOrEmpty(reference.Id))
                throw new ArgumentException("reference.Id cannot be null or empty", "reference");
            _reference = reference;
            _settings = StorageSettings.GetInstance().GetStorageProviderSettings<MemoryStorageProviderSettings>(reference.Id);
            if (_settings == null)
                throw new StorageSettingsException(string.Format("{0} provider settings not found", reference));
        }

        #endregion

        #region Properties

        private MemoryStorageProviderSettings Settings
        {
            get { return _settings; }
        }

        #endregion

        #region IStorageProvider Members

        public StorageProviderReference Reference
        {
            get { return _reference; }
        }

        public void Insert(StorageEntry entry)
        {
            _sync.AcquireWriterLock(WriterLockTimeout);
            try
            {
                if (_items.ContainsKey(entry.Key))
                    _items.Remove(entry.Key);
                _items.Add(entry.Key, entry);
            }
            finally
            {
                _sync.ReleaseWriterLock();
            }
        }

        public void Remove(string key)
        {
            _sync.AcquireWriterLock(WriterLockTimeout);
            try
            {
                if (_items.ContainsKey(key))
                    _items.Remove(key);
            }
            finally
            {
                _sync.ReleaseWriterLock();
            }
        }

        public StorageEntry Get(string key)
        {
            Scavenge();
            _sync.AcquireReaderLock(ReaderLockTimeout);
            try
            {
                if (_items.ContainsKey(key))
                {
                    _items[key].LastAccessTime = DateTime.Now;
                    return _items[key];
                }
                else
                    return null;
            }
            finally
            {
                _sync.ReleaseReaderLock();
            }
        }

        #endregion

        #region Scavenge

        private DateTime _lastScavenge = DateTime.MinValue;

        private void Scavenge()
        {
            try
            {                
                if (Settings.ScavengingInterval > TimeSpan.Zero && DateTime.Now.Subtract(_lastScavenge) > Settings.ScavengingInterval)
                {
                    _sync.AcquireWriterLock(WriterLockTimeout);
                    try
                    {
                        if (DateTime.Now.Subtract(_lastScavenge) > Settings.ScavengingInterval)
                        {
                            Dictionary<string, StorageEntry> sed = new Dictionary<string, StorageEntry>();
                            foreach (KeyValuePair<string, StorageEntry> pair in _items)
                            {
                                if (!pair.Value.IsExpired())
                                    sed.Add(pair.Key, pair.Value);
                            }
                            if (Settings.Capacity > 0 && sed.Values.Count > Settings.Capacity)
                            {
                                List<StorageEntry> sel = new List<StorageEntry>(sed.Values);
                                sel.Sort(delegate(StorageEntry se1, StorageEntry se2)
                                             {
                                                 return se1.LastAccessTime.CompareTo(se2.LastAccessTime);
                                             });
                                int i = 0;
                                while (sed.Count > Settings.Capacity)
                                {
                                    string k = sel[i].Key;
                                    if (sed.ContainsKey(k))
                                    {
                                        sed.Remove(k);
                                        i++;
                                    }
                                }
                            }
                            _items = sed;
                            _lastScavenge = DateTime.Now;
                        }
                    }
                    finally
                    {
                        _sync.ReleaseWriterLock();   
                    }
                }
            }
            catch
            {
                return;
            }
        }

        #endregion
    }
}
